package com.mxx.common.netty.http;

import com.mxx.common.conv.JacksonConverter;
import com.mxx.common.netty.NettyHttpOperator;
import com.mxx.common.netty.annotation.Http;
import com.mxx.common.netty.annotation.NettyHandler;
import com.mxx.common.netty.exception.ExceptionCatcherByHttp;
import com.mxx.common.netty.exception.IllegalMethodNotAllowedException;
import com.mxx.common.netty.exception.IllegalPathDuplicatedException;
import com.mxx.common.netty.exception.IllegalPathNotFoundException;
import com.mxx.common.netty.http.resolvers.NettyResolvers;
import com.mxx.common.netty.http.validate.ParameterValidator;
import com.mxx.common.netty.interceptor.Finish;
import com.mxx.common.netty.interceptor.NettyHttpInterceptor;
import com.mxx.common.utils.ClassUtil;
import io.netty.channel.ChannelFutureListener;
import io.netty.channel.ChannelHandlerContext;
import io.netty.handler.codec.http.FullHttpRequest;
import io.netty.util.ReferenceCountUtil;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.BeansException;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;

import java.lang.reflect.Method;
import java.util.*;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.function.BiFunction;
import java.util.function.Function;
import java.util.function.Predicate;
import java.util.stream.Stream;

/**
 * @Auther: Mr. Zeng
 * @Date: 2020/8/12 17:27
 * @Description: 暂无描述
 */
@Slf4j
public abstract class AbstractNettyHttpOperator implements NettyHttpOperator, ApplicationContextAware {
    /**
     * 根路径
     */
    private String contextPath;
    /**
     * 执行方法集合
     */
    private HashMap<NettyPath, NettyMethod> httpMethodMap = new HashMap<>();
    /**
     * 拦截器集合
     */
    private Set<NettyHttpInterceptor> interceptorSet = new TreeSet<>();
    /**
     * 参数转换器集合
     */
    private Collection<NettyResolvers> resolvers;
    /**
     * 参数校验器集合
     */
    private Collection<ParameterValidator> validates;
    /**
     * 返回值构造工厂
     */
    private NettyHttpResponseFactory responseFactory;
    /**
     * 序列化转换
     */
    private JacksonConverter jacksonConverter;

    /**
     * 路径与处理器的持有
     */
    private ExceptionCatcherByHttp exceptionCatcherByHttp;
    /**
     * 线程创造器
     */
    protected ExecutorService executor;

    public AbstractNettyHttpOperator(String contextPath, ExecutorService executor) {
        this.contextPath = contextPath;
        this.executor = executor;
    }


    /**
     * 主要执行方法
     *
     * @param ctx
     * @param request
     */
    @Override
    public void doRead(ChannelHandlerContext ctx, FullHttpRequest request) {
        executor.execute(new HttpRequestRun(ctx, request, this));
    }


    /**
     * 异常处理
     *
     * @param ctx
     * @param cause
     * @throws Exception
     */

    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
        exceptionCatcherByHttp.exception(ctx, cause);
    }

    /**
     * 初始化加载NettyHandle
     *
     * @param applicationContext
     */
    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {

        /**
         * 加载拦截器
         */
        loadInterceptor(applicationContext);
        /**
         * 加载参数转换器
         */
        loadParamResolver(applicationContext);

        /**
         * 加载参数校验器
         */
        loadValidates(applicationContext);
        /**
         * 加载方法映射
         */
        loadMappingMap(contextPath, applicationContext);

        this.responseFactory = applicationContext.getBean(NettyHttpResponseFactory.class);
        this.jacksonConverter = applicationContext.getBean(JacksonConverter.class);
        this.exceptionCatcherByHttp = applicationContext.getBean(ExceptionCatcherByHttp.class);
    }

    abstract NettyHttpResponse onReceivedRequest(ChannelHandlerContext context, NettyHttpRequest request);

    public Collection<NettyResolvers> getResolvers() {
        return resolvers;
    }

    public Collection<ParameterValidator> getValidates() {
        return validates;
    }

    public NettyHttpResponseFactory getResponseFactory() {
        return responseFactory;
    }

    /**
     * 拦截器执行
     *
     * @param context
     * @param request
     * @param function
     * @return
     */

    protected Finish doInterceptor(ChannelHandlerContext context, NettyHttpRequest request, NettyMethod processor, Function<NettyHttpInterceptor, BiFunction<NettyHttpRequest, NettyMethod, Finish>> function) {
        Iterator<NettyHttpInterceptor> iterator = interceptorSet.iterator();
        while (iterator.hasNext()) {
            NettyHttpInterceptor interceptor = iterator.next();
            Finish finish = function.apply(interceptor).apply(request, processor);
            if (finish.doIntercept()) {
                return finish;
            }
        }
        return Finish.SUCCESSFUL;
    }

    protected Finish doInterceptor(ChannelHandlerContext context, NettyHttpRequest request, NettyHttpResponse response, NettyMethod processor, Function<NettyHttpInterceptor, NettyHttpOperator.InterceptorExecute> function) {
        Iterator<NettyHttpInterceptor> interceptorIterator = interceptorSet.iterator();
        while (interceptorIterator.hasNext()) {
            NettyHttpInterceptor interceptor = interceptorIterator.next();
            Finish finish = function.apply(interceptor).doInterceptor(request, response, processor);
            if (finish.doIntercept()) {
                return finish;
            }
        }
        return Finish.SUCCESSFUL;
    }

    /**
     * 匹配请求地址
     *
     * @param request
     * @return
     * @throws IllegalPathNotFoundException
     * @throws IllegalMethodNotAllowedException
     */

    protected NettyMethod matchMethod(NettyHttpRequest request) throws IllegalPathNotFoundException, IllegalMethodNotAllowedException {

        AtomicBoolean matched = new AtomicBoolean(false);

        Stream<NettyPath> stream = httpMethodMap.keySet().stream()
                .filter(((Predicate<NettyPath>) netty -> {
                    /**
                     *过滤 Path URI 不匹配的
                     */
                    if (request.matched(netty.getUri(), netty.isEqual())) {
                        matched.set(true);
                        return matched.get();
                    }
                    return false;

                }).and(netty -> {
                    /**
                     * 过滤 Method 匹配的
                     */
                    return request.isAllowed(netty.getMethod());
                }));

        Optional<NettyPath> optional = stream.findFirst();

        stream.close();

        if (!optional.isPresent() && !matched.get()) {
            throw new IllegalPathNotFoundException();
        }

        if (!optional.isPresent() && matched.get()) {
            throw new IllegalMethodNotAllowedException();
        }

        return httpMethodMap.get(optional.get());
    }

    private static class HttpRequestRun implements Runnable {
        private ChannelHandlerContext context;
        private FullHttpRequest request;
        private AbstractNettyHttpOperator nettyHttpOperator;

        public HttpRequestRun(ChannelHandlerContext context, FullHttpRequest request, AbstractNettyHttpOperator nettyHttpOperator) {
            this.context = context;
            this.request = request;
            this.nettyHttpOperator = nettyHttpOperator;
        }

        @Override
        public void run() {
            NettyHttpResponse response;
            try {
                NettyHttpRequest nettyHttpRequest = new NettyHttpRequest(context, request, nettyHttpOperator.jacksonConverter);
                response = nettyHttpOperator.onReceivedRequest(context, nettyHttpRequest);
            } catch (Exception ex) {
                response = nettyHttpOperator.getResponseFactory().catchThrowable(ex);
            } finally {
                while (request.refCnt() != 0) {
                    ReferenceCountUtil.release(request);
                }
            }
            if (!response.hasContentType()) {
                response.withJson();
            }
            context.writeAndFlush(response).addListener(ChannelFutureListener.CLOSE);
        }
    }

    /**
     * 加载拦截器
     *
     * @param applicationContext
     */
    private void loadInterceptor(ApplicationContext applicationContext) {
        Map<String, NettyHttpInterceptor> interceptors = applicationContext.getBeansOfType(NettyHttpInterceptor.class);
        for (Map.Entry<String, NettyHttpInterceptor> entry : interceptors.entrySet()) {
            NettyHttpInterceptor interceptor = entry.getValue();
            interceptorSet.add(interceptor);

        }
        log.info(">>>>>> load interceptor complete . number [{}]", interceptorSet.size());
    }

    /**
     * 加载地址映射
     *
     * @param contextPath
     * @param applicationContext
     */
    private void loadMappingMap(String contextPath, ApplicationContext applicationContext) {

        Map<String, Object> handlers = applicationContext.getBeansWithAnnotation(NettyHandler.class);
        for (Map.Entry<String, Object> entry : handlers.entrySet()) {

            // 加载执行器
            List<java.lang.reflect.Method> methodList = ClassUtil.getMethodListWithAnnotation(entry.getValue().getClass(), Http.class);
            for (Method method : methodList) {
                Http http = ClassUtil.getAnnotation(method, Http.class);
                NettyPath netty = NettyPath.make(contextPath, http);
                if (httpMethodMap.containsKey(netty)) {
                    log.error("HttpMethod has duplicated :" + netty.toString(), new IllegalPathDuplicatedException(String.format("%s There are multiple", netty.toString())));
                    System.exit(0);
                }
                // 加载参数验证器
                NettyMethod nettyMethod = new NettyMethod(netty, entry.getValue(), method);
                nettyMethod.initMethodParameter(this.resolvers);
                nettyMethod.loadMValidator(validates);
                httpMethodMap.put(netty, nettyMethod);
            }
        }
        log.info(">>>>>> load http request method complete . number [{}]", httpMethodMap.size());
    }

    /**
     * 加载参数转换器
     *
     * @param applicationContext
     */
    private void loadParamResolver(ApplicationContext applicationContext) {
        Map<String, NettyResolvers> resolvers = applicationContext.getBeansOfType(NettyResolvers.class);
        this.resolvers = resolvers.values();
        log.info(">>>>>> load parameter resolvers  complete . number [{}]", resolvers.size());
    }

    /**
     * 加载参数验证器
     *
     * @param applicationContext
     */
    private void loadValidates(ApplicationContext applicationContext) {
        Map<String, ParameterValidator> parameterValidates = applicationContext.getBeansOfType(ParameterValidator.class);
        this.validates = parameterValidates.values();
        log.info(">>>>>> load parameter parameter validates  complete . number [{}]", parameterValidates.size());
    }
}
